package com.bite.system.Service.ServiceImpl;

import Constants.RedisConstant;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bite.system.Constants.Constant;
import com.bite.system.DTO.ExamAddDTO;
import com.bite.system.DTO.ExamDTO;
import com.bite.system.DTO.ExamEditDTO;
import com.bite.system.DTO.ExamQuestionDTO;
import com.bite.system.Mapper.ExamMapper;
import com.bite.system.Mapper.ExamQuestionMapper;
import com.bite.system.Mapper.QuestionMapper;
import com.bite.system.Service.ExamService;
import com.bite.system.VO.ExamDetailVO;
import com.bite.system.VO.ExamVO;
import com.bite.system.VO.QuestionVO;
import com.bite.system.domain.Exam;
import com.bite.system.domain.ExamQuestion;
import com.bite.system.domain.Question;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import domain.MyThreadLocal;
import domain.R;
import domain.TableDataInfo;
import enums.ResultCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.stream.Collectors;


@Service
public class ExamServiceImpl extends ServiceImpl<ExamQuestionMapper, ExamQuestion> implements ExamService {

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private QuestionMapper questionMapper;

    @Autowired
    private ExamQuestionMapper examQuestionMapper;

    @Autowired
    private StringRedisTemplate redis;

    @Override
    public TableDataInfo list(ExamDTO examDTO) {

        PageHelper.startPage(examDTO.getPage(), examDTO.getPageSize());
        List<ExamVO> list = examMapper.list(examDTO);
        if (list == null || list.size() == 0){
            return TableDataInfo.empty();
        }
        PageInfo<ExamVO> pageInfo = new PageInfo<>(list);
        return TableDataInfo.success(list, pageInfo.getTotal());
    }

    @Override
    public R<String> add(ExamAddDTO examAddDTO) {
        //根据题目查询是否已经存在了
        LambdaQueryWrapper<Exam> eq = new LambdaQueryWrapper<Exam>()
                .eq(Exam::getTitle, examAddDTO.getTitle());
        List<Exam> exams = examMapper.selectList(eq);
        if (exams == null || exams.size() > 0){
            return R.failed(ResultCode.FAILED_ALREADY_EXISTS);
        }

        //接着判断日期是否正确,开始时间大于当前时间
        if(examAddDTO.getStartTime().isBefore(LocalDateTime.now())){
            return R.failed(ResultCode.FAILED_START_TIME_LESS_THAN_NOW);
        }
        if(examAddDTO.getEndTime().isBefore(examAddDTO.getStartTime())){
            return R.failed(ResultCode.FAILED_END_TIME_LESS_THAN_START_TIME);
        }
        Exam exam = BeanUtil.copyProperties(examAddDTO, Exam.class);
        exam.setUpdateTime(LocalDateTime.now());
        exam.setCreateBy(Long.valueOf(MyThreadLocal.get()));
        exam.setUpdateBy(Long.valueOf(MyThreadLocal.get()));
        exam.setCreateTime(LocalDateTime.now());
        int insert = examMapper.insert(exam);
        if (insert > 0){
            return R.success(exam.getExamId().toString());
        }
        return R.failed();
    }

    @Override
    public R<Void> addQuestion(ExamQuestionDTO examQuestionDTO) {
        Long examId = examQuestionDTO.getExamId();
        //判断是否存在竞赛id
        Exam exam = examMapper.selectById(examId);
        if (exam == null){
            return R.failed(ResultCode.FAILED_NOT_EXISTS);
        }
        //判断竞赛是否已经开始
        if (exam.getStartTime().isBefore(LocalDateTime.now())){
            return R.failed(ResultCode.FAILED_EXAM_STARTED);
        }

        LinkedHashSet<Long> questionIds = examQuestionDTO.getQuestionIds();

        //判断是否存在题目id
        List<Question> questions = questionMapper.selectBatchIds(questionIds);
        if (questions == null || questions.size() < questionIds.size()){
            return R.failed(ResultCode.FAILED_NOT_EXISTS);
        }

        //避免题目重复添加
        LambdaQueryWrapper<ExamQuestion> eq = new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)
                .eq(ExamQuestion::getExamId, examId);
        List<ExamQuestion> questionList = examQuestionMapper.selectList(eq);
        List<Long> questionIdList = new ArrayList<>();
        questionList.stream().forEach(examQuestion -> {
            questionIdList.add(examQuestion.getQuestionId());
        });

        int i;
        //如果 questionList 集合中包含了questionIds的话，不能添加重复题目
        if (questionIdList != null && questionIdList.size() > 0){
            i = questionIdList.size()+1;
            for (Long questionId : questionIds) {
                if (questionIdList.contains(questionId)){
                    return R.failed(ResultCode.FAILED_QUESTION_EXISTS);
                }
            }
        }else {
            i = 1;
        }


        //添加到数据库中

        List<ExamQuestion> examQuestions = new ArrayList<>();
        for (Long questionId : questionIds) {
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setExamId(examId);
            examQuestion.setQuestionId(questionId);
            examQuestion.setQuestionOrder(i++);
            examQuestion.setCreateBy(Long.valueOf(MyThreadLocal.get()));
            examQuestion.setUpdateBy(Long.valueOf(MyThreadLocal.get()));
            examQuestion.setCreateTime(LocalDateTime.now());
            examQuestion.setUpdateTime(LocalDateTime.now());
            examQuestions.add(examQuestion);
        }

        if (saveBatch(examQuestions)){
            return R.success();
        }
        return R.failed();
    }

    @Override
    public R<ExamDetailVO> detail(Long examId) {
        ExamDetailVO detail = new ExamDetailVO();

        //根据竞赛id获取竞赛信息
        Exam exam = examMapper.selectById(examId);
        if (exam == null){
            return R.failed(ResultCode.FAILED_NOT_EXISTS);
        }
        BeanUtil.copyProperties(exam, detail);
        List<ExamQuestion> questionList = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId, ExamQuestion::getQuestionOrder)
                .eq(ExamQuestion::getExamId, examId)
                .orderByAsc(ExamQuestion::getExamQuestionId));
        if (questionList == null || questionList.size() == 0){
            return R.success(detail);
        }
        List<Long> questionIdList = questionList.stream().map(ExamQuestion::getQuestionId).toList();
        //根据题目id获取题目信息
        List<Question> questions = questionMapper.selectList(new LambdaQueryWrapper<Question>()
                .in(Question::getQuestionId, questionIdList));
        if (questions == null || questions.size() == 0){
            return R.failed(ResultCode.FAILED_NOT_EXISTS);
        }
        List<QuestionVO> questionVOS = BeanUtil.copyToList(questions, QuestionVO.class);
        detail.setQuestions(questionVOS);
        return R.success(detail);
    }

    @Override
    public R<Void> edit(ExamEditDTO examEditDTO) {

        //先根据竞赛Id查询
        Exam exam = examMapper.selectById(examEditDTO.getExamId());
        if (exam == null){
            return R.failed(ResultCode.FAILED_NOT_EXISTS);
        }
        if (exam.getStatus() == 1){
            return R.failed(ResultCode.FAILED_EXAM_STARTED);
        }

        BeanUtil.copyProperties(examEditDTO, exam);
        //检查字段是否符合规则
        List<Exam> exams = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .eq(Exam::getTitle, exam.getTitle())
                .ne(Exam::getExamId, exam.getExamId()));
        if (exams != null && exams.size() > 0){
            return R.failed(ResultCode.FAILED_QUESTION_EXISTS);
        }
        if (exam.getStartTime().isBefore(LocalDateTime.now())){
            return R.failed(ResultCode.FAILED_START_TIME_LESS_THAN_NOW);
        }
        if (exam.getEndTime().isBefore(exam.getStartTime())){
            return R.failed(ResultCode.FAILED_END_TIME_LESS_THAN_START_TIME);
        }

        //更新数据
        int i = examMapper.updateById(exam);
        if (i > 0){
            return R.success();
        }
        return R.failed();
    }

    @Override
    public R<Void> delete(Long examId, Long questionId) {
        //先根据竞赛Id查询
        Exam exam = examMapper.selectById(examId);
        if (exam == null){
            return R.failed(ResultCode.FAILED_NOT_EXISTS);
        }
        if (exam.getStatus() == 1){
            return R.failed(ResultCode.FAILED_EXAM_STARTED);
        }
        //找到竞赛信息后，判断当前时间是否在竞赛时间范围内
        if (exam.getStartTime().isBefore(LocalDateTime.now())){
            return R.failed(ResultCode.FAILED_EXAM_STARTED);
        }
        int delete = examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId)
                .eq(ExamQuestion::getQuestionId, questionId));
        if (delete > 0){
            return R.success();
        }
        return R.failed();
    }

    @Override
    @Transactional
    public R<Void> deleteExamQuestion(Long examId) {
        //先根据竞赛id查询竞赛信息
        Exam exam = examMapper.selectById(examId);
        if (exam == null){
            return R.failed(ResultCode.FAILED_NOT_EXISTS);
        }
        if (exam.getStatus() == 1){
            return R.failed(ResultCode.FAILED_EXAM_STARTED);
        }
        //判断当前删除的时间是否是竞赛开始时间之前
        if (exam.getStartTime().isBefore(LocalDateTime.now())){
            return R.failed(ResultCode.FAILED_EXAM_STARTED_CANNOT_DELETE);
        }
        //删除基本竞赛信息
        int deleteById = examMapper.deleteById(examId);
        //删除题目信息
        examQuestionMapper.deleteById(examId);
        if (deleteById > 0){
            return R.success();
        }
        return R.failed();
    }

    @Override
    public R<Void> publish(Long examId) {
        //先判断竞赛id是否存在
        Exam exam = examMapper.selectById(examId);
        if (exam == null){
            throw new RuntimeException("该竞赛信息不存在");
        }

        //判断该竞赛是否结束了
        if (exam.getEndTime().isBefore(LocalDateTime.now())){
            throw new RuntimeException("该竞赛信息已经结束，无法发布");
        }
        //接着判断该竞赛是否存在题目
        Long count = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId));
        if (count == null || count <= 0){
            throw new RuntimeException("该竞赛信息不存在题目");
        }
        //修改状态
        exam.setStatus(Constant.EXAM_TYPE_PUBLIC);

        int i = examMapper.updateById(exam);
        if (i > 0){
            //状态修改成功之后，需要将数据存放到redis中
            //1.0 首先将竞赛id放到未结束的redis列表中
            String unfinishedExamList = RedisConstant.UNFINISHED_EXAM_LIST;
            redis.opsForList().leftPush(unfinishedExamList, examId.toString());
            //2.0 接着将详情竞赛放入到redis中
            String examDetailKey = RedisConstant.EXAM_DETAIL_KEY;
            String examStr = JSONUtil.toJsonStr(exam);
            redis.opsForValue().set(examDetailKey+examId, examStr);
            return R.success();
        }
        return R.failed();
    }

    @Override
    public R<Void> quash(Long examId) {
        //判断竞赛是否存在
        Exam exam = examMapper.selectById(examId);
        if (exam == null){
            throw new RuntimeException("该竞赛信息不存在");
        }
        //判断竞赛是否已经结束
        if (exam.getEndTime().isBefore(LocalDateTime.now())){
            throw new RuntimeException("该竞赛信息已经结束，无法撤销");
        }
        //判断竞赛是否已经开始了
        if (exam.getStartTime().isBefore(LocalDateTime.now())){
            throw new RuntimeException("该竞赛信息已经开始，无法撤销");
        }
        exam.setStatus(Constant.EXAM_TYPE_QUASH);
        int i = examMapper.updateById(exam);
        if (i > 0){
            //撤销成功之后，需要将redis中的数据删除
            //1.0先从未完成redis中的列表删除
            String unfinishedExamList = RedisConstant.UNFINISHED_EXAM_LIST;
            redis.opsForList().remove(unfinishedExamList, 1L, examId.toString());
            //2.0接着从redis中删除详情
            String examDetailKey = RedisConstant.EXAM_DETAIL_KEY;
            redis.delete(examDetailKey+examId);
            //3.0最后将竞赛题目id列表中数据清空
            redis.delete(RedisConstant.EXAM_QUESTION_ID_KEY+examId);
            return R.success();
        }
        return R.failed();
    }
}
